﻿using System;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Represents one repetition ending.
    /// </summary>
    public class EndingElement : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="EndingElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="RepetitionElement"/>
        /// </para>
        /// <para>
        /// The repetition that directly owns this ending.
        /// </para>
        /// </param>
        /// <param name="number">
        /// <para>
        /// Type: <see cref="System.Byte"/>
        /// </para>
        /// <para>
        /// The number of this ending.
        /// </para>
        /// </param>
        /// <param name="firstMeasure">
        /// <para>
        /// Type: <see cref="System.UInt16"/>
        /// </para>
        /// <para>
        /// The number of the first measure of the ending.
        /// </para>
        /// </param>
        /// <param name="lastMeasure">
        /// <para>
        /// Type: <see cref="System.UInt16"/>
        /// </para>
        /// <para>
        /// The number of the last measure of the ending.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ValidationException">
        /// The value of <paramref name="number"/>, <paramref name="firstMeasure"/> or <paramref name="lastMeasure"/>
        /// is not valid. Please, refer to the documentation of the respective properties for details.
        /// </exception>
        internal EndingElement(CompositionPart packagePart, RepetitionElement owner, byte number, MeasureElement firstMeasure, MeasureElement lastMeasure)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            Number = number;
            FirstMeasure = firstMeasure;
            LastMeasure = lastMeasure;
        }

        /// <summary>
        /// Creates a new <see cref="EndingElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="RepetitionElement"/>
        /// </para>
        /// <para>
        /// The repetition that directly owns this ending.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal EndingElement(CompositionPart packagePart, RepetitionElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            numberAttribute = element.Attributes[PackageVocabulary.NumberAttribute];
            firstMeasureAttribute = element.Attributes[PackageVocabulary.FirstMeasureAttribute];
            lastMeasureAttribute = element.Attributes[PackageVocabulary.LastMeasureAttribute];
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.EndingElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            firstMeasureAttribute = document.CreateAttribute(PackageVocabulary.FirstMeasureAttribute);
            lastMeasureAttribute = document.CreateAttribute(PackageVocabulary.LastMeasureAttribute);
            numberAttribute = document.CreateAttribute(PackageVocabulary.NumberAttribute);

            element.Attributes.Append(firstMeasureAttribute);
            element.Attributes.Append(lastMeasureAttribute);
            element.Attributes.Append(numberAttribute);

            return element;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the number of the first measure of this ending.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.UInt16"/>
        /// </para>
        /// <para>
        /// The number (1-based index) of the first measure of the ending.
        /// </para>
        /// <para>
        /// The ending is in fact a continuous range of measures given by [<see cref="FirstMeasure"/>, <see cref="LastMeasure"/>].
        /// The repetition endings cannot overlap (within their owning repetition nor in the composition scope).
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> is equal to 0 or greater than the biggest measure number in the composition.<br/>
        /// -or-<br/>
        /// The <paramref name="value"/> is greater than <see cref="LastMeasure"/>.<br/>
        /// -or-<br/>
        /// The range [<paramref name="value"/>, <see cref="LastMeasure"/>] overlaps with another repetition ending.
        /// </exception>
        /// <seealso cref="LastMeasure"/>
        /// <seealso cref="RepetitionElement"/>
        /// <seealso cref="CompositionElement"/>
        /// TODO: Write the validation rules
        public MeasureElement FirstMeasure
        {
            get 
            {
                if (firstMeasure == null)
                    firstMeasure = PackagePart.Package.IDMap.FindObject<MeasureElement>(new Guid(firstMeasureAttribute.Value));

                return firstMeasure;
            }
            set
            {
                ValidateAndThrow("FirstMeasure", value);

                firstMeasure = value;
                firstMeasureAttribute.Value = value.ID.ToString();
                RaisePropertyChanged("FirstMeasure");
            }
        }

        /// <summary>
        /// Gets or sets the last measure of this ending.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The last measure of the ending.
        /// </para>
        /// <para>
        /// The ending is in fact a continuous range of measures given by [<see cref="FirstMeasure"/>, <see cref="LastMeasure"/>].
        /// The repetition endings cannot overlap (within their owning repetition nor in the composition scope).
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> does not belong to the composition.<br/>
        /// -or-<br/>
        /// The <paramref name="value"/> is less than <see cref="FirstMeasure"/>.<br/>
        /// -or-<br/>
        /// The range [<see cref="FirstMeasure"/>, <paramref name="value"/>] overlaps with another repetition ending.
        /// </exception>
        /// <seealso cref="FirstMeasure"/>
        /// <seealso cref="RepetitionElement"/>
        /// <seealso cref="CompositionElement"/>
        /// TODO: Write the validation rules
        public MeasureElement LastMeasure
        {
            get 
            {
                if (lastMeasure == null)
                    lastMeasure = PackagePart.Package.IDMap.FindObject<MeasureElement>(new Guid(lastMeasureAttribute.Value));

                return lastMeasure;
            }
            set
            {
                ValidateAndThrow("LastMeasure", value);

                lastMeasure = value;
                lastMeasureAttribute.Value = value.ID.ToString();
                RaisePropertyChanged("LastMeasure");
            }
        }

        /// <summary>
        /// Gets the number of this ending.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Byte"/>
        /// </para>
        /// <para>
        /// This value is a 1-based index of this ending within its repetition.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The number is equal to 0.<br/>
        /// -or-<br/>
        /// The owning repetition already contains another ending with the same number.
        /// </exception>
        /// <seealso cref="RepetitionElement"/>
        /// TODO: Write the validation rules.
        public byte Number
        {
            get { return byte.Parse(numberAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                ValidateAndThrow("Number", value);

                numberAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);
                RaisePropertyChanged("Number");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds a reference to the first measure.
        /// </summary>
        private MeasureElement firstMeasure;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="FirstMeasure"/> property.
        /// </summary>
        private XmlAttribute firstMeasureAttribute;
        /// <summary>
        /// Holds a reference to the last measure.
        /// </summary>
        private MeasureElement lastMeasure;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="LastMeasure"/> property.
        /// </summary>
        private XmlAttribute lastMeasureAttribute;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="Number"/> property.
        /// </summary>
        private XmlAttribute numberAttribute;

        #endregion
    }
}
